home *** CD-ROM | disk | FTP | other *** search
/ Night Owl 6 / Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso / 025a / cbase101.zip / GUIDE.TXT < prev    next >
Text File  |  1990-06-21  |  57KB  |  1,538 lines

  1.                          cbaseTM
  2.  
  3.                 The C Database Library
  4.  
  5.  
  6.  
  7.  
  8.  
  9.                         Citadel
  10.                   Brookville, Indiana Copyright  1989 by Citadel.  All rights reserved.
  11.  
  12. Citadel
  13. 241 East Eleventh Street
  14. Brookville, IN 47012
  15. 317-647-4720
  16. BBS 317-647-2403
  17.  
  18. Version 1.0.1
  19.  
  20. This manual is protected by United States copyright law.  No part of it
  21. may be reproduced without the express written permission of Citadel.
  22.  
  23. Technical Support
  24. The Citadel BBS is available 24 hours a day.  Voice support is
  25. available between 10 a.m. and 4 p.m. EST.  When calling for technical
  26. support, please have ready the following information:
  27.  
  28.           - product name and version number
  29.           - operating system and version number
  30.           - C compiler and version number
  31.           - computer brand and model
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57. UNIX is a trademark of AT&T.  MS-DOS is a trademark of Microsoft
  58. Corporation.  Turbo C is a trademark of Borland International, Inc.
  59.  
  60.  
  61.  
  62.  
  63.                                               Contents
  64.  
  65.  
  66. Chapter 1.  Introduction                             1
  67.  
  68. Chapter 2.  Database Basics                          3
  69.      2.1  Sequential File Structures
  70.      2.2  Inverted Files
  71.      2.3  B-trees
  72.  
  73. Chapter 3.  cbase Library Functions                  7
  74.      3.1  Access Control Functions
  75.      3.2  Lock Functions
  76.      3.3  Record Cursor Position Functions
  77.      3.4  Key Cursor Position Functions
  78.      3.5  Input/Output Functions
  79.      3.6  Data Import/Export Functions
  80.  
  81. Chapter 4.  An Example Program                      15
  82.      4.1  Data Definition
  83.      4.2  Opening a cbase
  84.      4.3  Locking a cbase
  85.      4.4  Accessing a cbase
  86.      4.5  Closing a cbase
  87.      4.6  Manipulating Exported Data
  88.  
  89. Appendix A.  Installation Instructions              27
  90.      A1   The blkio Library
  91.      A2   The btree Library
  92.      A3   The lseq Library
  93.      A4   The cbase library
  94.  
  95. Appendix B.  Defining New Data Types                33
  96.      B1   The Type Name
  97.      B2   The Comparison Function
  98.      B3   The Export and Import Functions
  99.      B4   The Type Count
  100.  
  101. Appendix C.  Porting to a New Operating System      37
  102.      C1   The HOST macro
  103.      C2   The File Descriptor Type
  104.      C3   File Access System Calls
  105.      C4   File Locking System Calls
  106.  
  107. References                                          41
  108.  
  109.  
  110.  
  111.  
  112.                               Chapter 1:  Introduction
  113.  
  114.  
  115.      cbase is a C database file management library.  Records may be
  116. accessed both randomly and sequentially through indexes stored in 
  117. B+-trees.  Records may also be accessed sequentially in the order in
  118. which they are stored.  Multiuser access is supported under any
  119. operating system with file locking capabilities.
  120.  
  121.      cbase is designed for portability.  It is written in strict adherence
  122. to the ANSI C standard while retaining compatibility with with K&R C
  123. compilers.  All system dependent code is isolated in order to make
  124. porting easy.
  125.  
  126.      Many of the operations performed by cbase internally represent
  127. independently useful tools, and the software has been designed with this
  128. in mind.  cbase actually comprises four individual libraries, each
  129. complete and independently accessible.  Figure 1.1 shows these libraries
  130. and their relationships.
  131.  
  132.           ┌─────────────────────────────────┐
  133.           │              cbase              │
  134.           └───────┬─────────────────┬───────┘
  135.           ┌───────┴───────┐ ┌───────┴───────┐
  136.           │     lseq      │ │     btree     │
  137.           └───────┬───────┘ └───────┬───────┘
  138.           ┌───────┴─────────────────┴───────┐
  139.           │              blkio              │
  140.           └─────────────────────────────────┘
  141.  
  142.       Figure 1.1. cbase and Underlying Libraries
  143.  
  144.      At the foundation of cbase is the blkio (block buffered I/O) library. 
  145. blkio is a buffered I/O library similar to stdio, but based on a file
  146. model more appropriate for structured files such as used in database
  147. software.  While stdio models a file as an unstructured stream of
  148. characters, blkio models a file as a collection of blocks made up of
  149. fields (see FROS89 for a complete description of blkio).
  150.  
  151.      The lseq (linked sequential file) library provides all the facilities
  152. necessary for the creation and manipulation of doubly linked sequential
  153. files.  The btree (B-tree) library provides the same for B+-tree files. 
  154. The cbase library uses lseq and btree to perform all structured file
  155. management operations.  The lseq library is used for record storage and
  156. the btree library for inverted file key storage.
  157.  
  158.      When using a particular library, all operations are performed with
  159. functions provided by that library.  No references need be made to
  160. underlying libraries.  When using the cbase library, it is therefore not
  161. necessary to know the functions included in the other libraries.
  162.  
  163.  
  164.  
  165.  
  166.                            Chapter 2:  Database Basics
  167.  
  168.  
  169.      This chapter describes some of the basic database concepts relevant
  170. to cbase.  It is intended to be a brief and readily accessible
  171. introduction, and can be easily skipped by one already familiar with
  172. database fundamentals.  References are given where in-depth discussions
  173. may be found.
  174.  
  175.  
  176. 2.1  Sequential File Structures
  177.  
  178.      One of the simplest file organizations is the physical sequential
  179. file.  In this organization, records are simply written one after the other
  180. and sequential access is done in the order in which the records are
  181. physically stored.
  182.  
  183.      The physical sequential file works very well in cases where the
  184. data is static, but it is extremely inefficient when the data is dynamic. 
  185. Consider a file containing 100,000 records stored in a sorted order. 
  186. The insertion of a single record at the beginning of this file would
  187. result in 100,000 records being moved to make room at the beginning
  188. of the file for the new record.
  189.  
  190.      This identical problem also occurs when large ordered lists are
  191. stored in memory, and the same solution, the linked list, can be used
  192. for files (see pp. 106-12 of HORO76 for an explanation of linked lists
  193. in memory).  In a linked list, the record order is determined by pointers
  194. stored with each record, not by the physical record locations; a record
  195. logically located at the beginning of a file can be physically located
  196. anywhere within the file.  In a singly linked list each record has only a
  197. pointer to the next record and so the file can be traversed in just one
  198. direction.  In a doubly linked list, each record has both a next and a
  199. previous pointer, allowing bidirectional access.
  200.  
  201.  
  202. 2.2  Inverted Files
  203.  
  204.      Assume a data file containing member records for an organization,
  205. and that the record format for this file is
  206.  
  207.           typedef struct {
  208.                long number;
  209.                char name[24];
  210.                char address[81];
  211.                char city[24];
  212.                char state[3];
  213.                char zip[11];
  214.           } member_t;
  215.  
  216. Assume further that the data file has a physical sequential file structure,
  217. and that the records are sorted by the number field, therefore a binary
  218. search may be performed to quickly find a record with a given number
  219. field.  The field that the records are sorted on is called the primary
  220. index.  The problem arises when a query is made on another field
  221. besides the primary index, in which case a search of the entire data file
  222. is required.
  223.  
  224.     The problem of being able to perform queries efficiently on more
  225. than one field can be resolved through the use of inverted files.  For
  226. each secondary index exists an inverted file containing an entry for each
  227. record in the data file.  The inverted file for the name field, for
  228. example, would have entries containing the name field and record
  229. position, and these would be sorted by the name field.  To find a
  230. record with a given value in the name field, the inverted file for the
  231. name field would be searched.  The record position paired with the
  232. specified name value would then be used to locate the record in the
  233. data file.  See pp. 531-533 of HORO76 and pp. 75-78 of ULLM82 for
  234. more information on inverted files.
  235.  
  236.  
  237. 2.3  B-trees
  238.  
  239.      As discussed above, the physical sequential file provides
  240. unacceptable performance for dynamic data.  For static data, however, it
  241. provides efficient sequential and passable random access (using a binary
  242. search).  The linked sequential file solves the problem of performance
  243. for dynamic data while preserving efficient sequential access, but the
  244. ability to randomly access records is lost.  The need to store dynamic
  245. data which may be randomly accessed led to the development of the
  246. B-tree.
  247.  
  248.      As the name implies, the B-tree stores data in a tree structure,
  249. which allows random access.  And since the tree nodes are connected
  250. by pointers, the update problem is solved in the same way as by the
  251. linked sequential file organization.  The basic B-tree does not provide
  252. very efficient sequential access, however.  This led to a B-tree variant
  253. called the B+-tree.  The  B+-tree incorporates a linked list into its
  254. structure to achieve efficient sequential as well as random access.
  255.  
  256.      While the B+-tree does come close to providing the best of all
  257. worlds, it has two important drawbacks.  First, there is significantly
  258. more storage overhead for a B+-tree than for a sequential file.  Every
  259. entry in a B+-tree is stored in a leaf node, and the rest of the tree is
  260. simply scaffolding used when the tree is searched.  Second, each entry
  261. in any type of B-tree must be unique.  These characteristics make
  262. B-tree file organizations inappropriate for data files containing records,
  263. which may be both large and duplicated.  But for inverted files,
  264. generally having relatively short entries that are by their very nature
  265. unique (because the file position of each record in the data file would
  266. be unique), the B+-tree is an ideal choice.  cbase therefore uses the
  267. linked sequential file organization for record storage and B+-trees for
  268. inverted files.
  269.  
  270.      The mechanics of B-trees is beyond the scope of this chapter.  A
  271. good discussion of the B-tree and its major variants may be found in
  272. COME79.
  273.  
  274.  
  275.  
  276.  
  277.                    Chapter 3:  cbase Library Functions
  278.  
  279.  
  280. 3.1  Access Control Functions
  281.  
  282.      The cbcreate function is used to create a new cbase.
  283.  
  284.   int cbcreate(const char *cbname, size_t recsize,
  285.             int fldc, const cbfield_t fldv[]);
  286.  
  287. cbname points to a character string which is the name of the cbase. 
  288. This name is used as the name of the data file containing the records in
  289. the cbase.  recsize specifies the record size to be used.  fldc is the
  290. number of fields in the cbase, and fldv is an array of fldc field
  291. definition structures.  Each structure in the array contains the definition
  292. for one field.   field definitions must be in the order the fields occur in
  293. the record.  The field definition structure type cbfield_t is defined
  294. in <cbase.h>.
  295.  
  296.     typedef struct {    /* field definition */
  297.         size_t offset;      /* field offset */
  298.         size_t size;        /* size of field */
  299.         int type;           /* type of field */
  300.         int flags;          /* flags */
  301.         char filename[FILENAME_MAX + 1];
  302.                             /* data file name */
  303.     } cbfield_t;
  304.  
  305. offset is the location of the field within the record and size is the
  306. size of the field.  type specifies the field data type, legal values for
  307. which are shown in Table 3.1.  flags values are constructed by
  308. bitwise ORing together flags from the following list.
  309.  
  310.     CB_FKEY         Field is to be a key.
  311.     CB_FUNIQ        Only for use with CB_FKEY.
  312.                     Indicates that the key is
  313.                     constrained to be unique.
  314.  
  315. FILENAME_MAX is an ANSI C definition indicating the maximum
  316. length of a filename string.  It is defined in <stdio.h>.
  317.  
  318.     t_char      signed character
  319.     t_charv     signed character array
  320.     t_uchar     unsigned character
  321.     t_ucharv    unsigned character array
  322.     t_short     signed short integer
  323.     t_shortv    signed short integer array
  324.     t_ushort    unsigned short integer
  325.     t_ushortv   unsigned short integer array
  326.     t_int       signed integer
  327.     t_intv      signed integer array
  328.     t_uint      unsigned integer
  329.     t_uintv     unsigned integer array
  330.     t_long      signed long integer
  331.     t_longv     signed long integer array
  332.     t_ulong     unsigned long integer
  333.     t_ulongv    unsigned long integer array
  334.     t_float     floating point
  335.     t_floatv    floating point array
  336.     t_double    double precision
  337.     t_doublev   double precision array
  338.     t_ldouble   long double
  339.     t_ldoublev  long double array
  340.     t_pointer   pointer
  341.     t_string    character string
  342.     t_cistring  case-insensitive character string
  343.     t_binary    block of binary data (e.g., graphics)
  344.  
  345.               Table 3.1. cbase Data Types
  346.  
  347.      The first step in accessing an existing cbase is to open it, which is
  348. done using the function
  349.  
  350.     cbase_t *cbopen(const char *cbname, const char
  351.          *type, int fldc, const cbfield_t fldv[]);
  352.  
  353. cbname, fldc, and fldv are the same as for cbcreate, and must
  354. be given the same values as when the cbase was created.  type points
  355. to a character string specifying the type of access for which the cbase
  356. is to be opened (as for the stdio function fopen).  Legal values for
  357. type are
  358.  
  359.     "r"       open for reading
  360.     "r+"      open for update (reading and writing)
  361.  
  362. cbopen returns a pointer to the open cbase.
  363.  
  364.      The cbsync function causes any buffered data for a cbase to be
  365. written out.
  366.  
  367.     int cbsync(cbase_t *cbp);
  368.  
  369. The cbase remains open and the buffers retain their contents.
  370.  
  371.      After processing is completed on an open cbase, it must be closed
  372. using the function
  373.  
  374.     int cbclose(cbase_t *cbp);
  375.  
  376. The cbclose function causes any buffered data for the cbase to be
  377. written out, unlocks it, closes it, and frees the cbase pointer.
  378.  
  379.  
  380. 3.2  Lock Functions
  381.  
  382.      Before an open cbase can be accessed, it must be locked.  The
  383. function used to control the lock status of a cbase is
  384.  
  385.     int cblock(cbase_t *cbp, int ltype);
  386.  
  387. where cbp is a pointer to an open cbase and ltype is the lock type
  388. to be placed on the cbase.  The legal values for ltype are
  389.  
  390.     CB_RDLCK  lock cbase for reading
  391.     CB_WRLCK  lock cbase for reading and writing
  392.     CB_RDLKW  lock cbase for reading (wait)
  393.     CB_WRLKW  lock cbase for reading and writing (wait)
  394.     CB_UNLCK  unlock cbase
  395.  
  396. If ltype is CB_RDLCK and the cbase is currently write locked by
  397. another process, or if ltype is CB_WRLCK and the cbase is currently
  398. read or write locked by another process, cblock will fail and set
  399. errno to EAGAIN.  For the wait lock types, cblock will not return
  400. until the lock is available.
  401.  
  402.      The cbgetlck function reports the lock status held by the calling
  403. process on a cbase.
  404.  
  405.      int cbgetlck(cbase_t *cbp);
  406.  
  407. It returns one of the legal values for the ltype argument in the
  408. cblock function.
  409.  
  410.  
  411. 3.3  Record Cursor Position Functions
  412.  
  413.      Each open cbase has a record cursor.  At any given time the
  414. record cursor is positioned either on a record in that cbase or on a
  415. special position called null.  The record on which the cursor is located
  416. is referred to as the current record.  The operations performed by most
  417. cbase functions are either on or relative to the current record, so the
  418. initial step in a transaction on a cbase is usually to position the record
  419. cursor on the desired record.  When accessing the records in a cbase in
  420. the order that they are stored, the following functions are used to move
  421. the record cursor.
  422.  
  423.     int cbrecfirst(cbase_t *cbp);
  424.     int cbreclast(cbase_t *cbp);
  425.     int cbrecnext(cbase_t *cbp);
  426.     int cbrecprev(cbase_t *cbp);
  427.  
  428. The cbrecfirst function positions the record cursor to the first
  429. record, and cbreclast to the last record.  Before calling either of
  430. these functions cbreccnt should be used to test if the cbase is empty.
  431.  
  432.     unsigned long cbreccnt(cbase_t *cbp);
  433.  
  434. If the cbase is empty, there is no first or last record and so these
  435. functions would return an error.  The cbrecnext function advances
  436. the record cursor to the succeeding record, and cbrecprev retreats it
  437. to the preceding record.  In the record ordering, null is located before
  438. the first record and after the last.
  439.  
  440.      There are also functions for saving the current position of the
  441. record cursor and resetting it to that position.
  442.  
  443.    int cbgetrcur(cbase_t *cbp, cbrpos_t *cbrposp);
  444.    int cbsetrcur(cbase_t *cbp, const
  445.                                cbrpos_t*cbrposp);
  446.  
  447. The cbgetrcur function gets the current position of the record cursor
  448. and saves it in the variable pointed to by cbrposp.  cbrpos_t is the
  449. cbase record position type, defined in <cbase.h>.  cbsetrcur can
  450. then be used later to set the record cursor back to that position.  The
  451. record cursor can be positioned on null by passing cbsetrcur the
  452. NULL pointer rather than a pointer to a variable.  Other than this
  453. special case, cbsetrcur should only be called with record cursor
  454. positions previously saved with cbgetrcur.
  455.  
  456.      The cbrcursor macro is used to test if the record cursor for a
  457. cbase is positioned on a record or on null.
  458.  
  459.     void *cbrcursor(cbase_t *cbp);
  460.  
  461. If the record cursor of the cbase pointed to by cbp is positioned on
  462. null, cbrcursor returns the NULL pointer.  If it is on a record,
  463. cbrcursor returns a value not equal to the NULL pointer.  This
  464. function is useful for loops needing to test when the last (or first)
  465. record has been reached. 
  466.      The cbrecalign function aligns the record cursor with a
  467. specified key cursor.
  468.  
  469.     int cbrecalign(cbase_t *cbp, int field);
  470.  
  471. field is the key with which to align the record cursor.  The
  472. relationship between the key cursors and the record cursor is explained
  473. in the next section.
  474.  
  475.  
  476. 3.4  Key Cursor Position Functions
  477.  
  478.      In addition to a record cursor, each open cbase also has a key
  479. cursor for each key defined for that cbase.  Like the record cursor, a
  480. key cursor is positioned either on a record in that cbase or on null.  To
  481. access a cbase in the sort order of a certain key, the appropriate key
  482. cursor is used instead of the record cursor.  Each key cursor moves
  483. independently of the others, but whenever a key cursor position is set,
  484. the record cursor is moved to the same record.  The key cursors are
  485. not affected by moving the record cursor.
  486.  
  487.      The following functions are used to move a key cursor.
  488.  
  489.     int cbkeyfirst(cbase_t *cbp, int field);
  490.     int cbkeylast(cbase_t *cbp, int field);
  491.     int cbkeynext(cbase_t *cbp, int field);
  492.     int cbkeyprev(cbase_t *cbp, int field);
  493.  
  494. These perform as do the corresponding functions for the record cursor. 
  495. Note that the key cursor functions can be used only with fields defined
  496. to be keys (see cbcreate in section 3.1).
  497.  
  498.      The following function is used to search for a key of a certain 
  499. value.
  500.  
  501.     int cbkeysrch(cbase_t *cbp, int field,
  502.                     const void *buf);
  503.  
  504. field is the key to search for the data item pointed to by buf.  If
  505. the key is found, 1 is returned and the key and record cursors
  506. positioned to the record having that key.  If there is no record with that
  507. key, 0 is returned and the key and record cursor positioned to the
  508. record (possibly null) that would follow a record with that key value.
  509.  
  510.      Since the key cursors do not automatically follow the record
  511. cursor, the situation sometimes occurs where the record cursor is
  512. positioned to the desired record, but the cursor for the key to be used
  513. next is not.  The cbkeyalign function is used to align a specified
  514. key cursor with the record cursor.
  515.  
  516.     int cbkeyalign(cbase_t *cbp, int field);
  517.  
  518. The reason the key cursors are not updated every time the record cursor
  519. moves is not because it would be in any way difficult to do so, but
  520. because this would increase the overhead enormously.  And since only
  521. one key cursor is normally used at a time, this extra overhead would
  522. almost never provide any benefit in return.
  523.  
  524.      As for the record cursor, each key cursor position can be tested to
  525. be positioned on a record or on null.
  526.  
  527.     void *cbkcursor(cbase_t *cbp, int field);
  528.  
  529. If the key cursor specified by field of the cbase pointed to by cbp is
  530. positioned on null, cbkcursor returns the NULL pointer.  If it is on a
  531. record, cbkcursor returns a value not equal to the NULL pointer.
  532.  
  533.  
  534. 3.5  Input/Output Functions
  535.  
  536.      To read a record from a cbase, the record cursor for that cbase is
  537. first positioned to the desired record using either the record cursor
  538. position functions or the key cursor position functions.  One of the
  539. following functions is then called to read from the current record.
  540.  
  541.    int cbgetr(cbase_t *cbp, void *buf);
  542.    int cbgetrf(cbase_t *cbp, int field, void *buf);
  543.  
  544. cbp is a pointer to an open cbase and buf points to the storage area
  545. to receive the data read from the cbase.  The cbgetr function reads
  546. the entire current record, while cbgetrf reads the specified field
  547. from the current record.
  548.  
  549.      The function for inserting a new record into a cbase is
  550.  
  551.     int cbinsert(cbase_t *cbp, const void *buf);
  552.  
  553. where buf points to the record to be inserted.  When a new record is
  554. inserted into a cbase, the position it holds relative to each key cursor is
  555. defined by the sort order for that key field.  There is no predefined sort
  556. order associated with the record cursor, however, and it is up to the
  557. user whether or not to store the records for each cbase in a sorted or
  558. unsorted order.  To store records in a sorted order, the record cursor is
  559. first positioned the the record after which to insert the new record. 
  560. cbinsert is then called to insert the record pointed to by buf after
  561. the current record.  If no sort order is desired, the step to position the
  562. record cursor is skipped.
  563.  
  564.      The cbdelcur function is used to delete a record.
  565.  
  566.     int cbdelcur(cbase_t *cbp);
  567.  
  568. The record cursor must first be positioned on the record to delete, then
  569. cbdelcur called to delete the current record.
  570.  
  571.      The cbputr function writes over an existing record.
  572.  
  573.     int cbputr(cbase_t *cbp, const void *buf);
  574.  
  575. buf points to the new record contents.  Writing over an existing record
  576. is equivalent to deleting the record and inserting a new one in the same
  577. position in the file.  If the new record contains an illegal duplicate key,
  578. this will cause the insert to fail, resulting in the record having been
  579. deleted from the cbase.  The exact behaviour that a program should
  580. have in such a circumstance is different for different applicatons, and so
  581. it is often desirable to use cbdelcur and cbinsert directly rather
  582. than cbputr.
  583.  
  584.  
  585. 3.6  Data Import/Export Functions
  586.  
  587.      cbase data can be exported to a text file using the cbexport
  588. function.
  589.  
  590.   int cbexport(cbase_t *cbp, const char *filename);
  591.  
  592. Every record in cbase cbp is converted to a text format and written to
  593. the file filename.  The export file format is defined as follows.
  594.  
  595.     - Each record is terminated by a newline ('\n').
  596.     - The fields in a record are delimited by vertical
  597.       bars ('|').
  598.     - Each field contains only printable characters.
  599.     - If a field contains the field delimiter
  600.       character, that character is replaced with \F.
  601.     - The individual elements of array data types are
  602.       exported as individual fields.
  603.  
  604.      Data may be imported from a text file using the cbimport
  605. function.
  606.  
  607.   int cbimport(cbase_t *cbp, const char *filename);
  608.  
  609. cbimport reads each record from the text file filename and inserts
  610. it into the cbase cbp.
  611.  
  612.      Data import/export is primarily used to move data between
  613. different database formats.  This sometimes requires some slight
  614. rearranging of the text before importing.  This can be most easily done
  615. using awk, a language designed specifically for manipulating records
  616. stored in text files.  awk is normally included with UNIX, and is also
  617. available for MS-DOS.
  618.  
  619.  
  620.  
  621.                         Chapter 4:  An Example Program
  622.  
  623.  
  624.      Included with cbase is rolodeck, an example program illustrating
  625. the use of cbase.  Rolodeck is a program for storing business cards. 
  626. To allow it to be compiled without requiring any additional libraries for
  627. displays, and because the purpose of the program is purely instructional,
  628. the program has been given only a simple user interface.
  629.  
  630.  
  631. 4.1  Data Definition
  632.  
  633.      The first step in writing a program using cbase is to define the
  634. data to be stored.  This should be done in a separate header file for
  635. each record type to be used.  Figure 4.1 shows rolodeck.h, the data
  636. definition header for the business card record type used by the rolodeck
  637. program.
  638.  
  639.     Starting at the top, the first thing to note is the definition of a
  640. macro to prevent multiple includes.  This is a general practice
  641. applicable to any header file, cbase or otherwise, whose purpose is to
  642. allow a header to be specified for inclusion multiple times in a file
  643. while ensuring that the definitions within the file are processed only
  644. once.  For a header containing nothing but macro definitions, the only
  645. effect of preventing multiple includes will be reduced compile times. 
  646. But for a header containing, for instance, type definitions, multiple
  647. includes would produce compilation errors.  Notice how the include
  648. macro is related to the header file name; the file name is converted to
  649. upper case and the period replaced by an underscore ('.' is not a valid
  650. character for C identifiers).  These macros should be named consistently
  651. to minimize the possibility of accidentally defining the same macro
  652. elsewhere for some other purpose.
  653.  
  654.      At the beginning of every data definition header file, <cbase.h>
  655. should be included.  Following this is defined the name of the cbase. 
  656. This character string is the name used for the data file.  This macro is
  657. to be used for the cbname argument when calling cbcreate and
  658. cbopen. #ifndef ROLODECK_H  /* prevent multiple includes */
  659. #define ROLODECK_H
  660.  
  661. #include <cbase.h>
  662.  
  663. /* cbase name */
  664. #define ROLODECK    ("rolodeck.dat")
  665.  
  666. /* record definition */
  667. typedef struct {
  668.     char rd_contact[41];    /* contact name */
  669.     char rd_title[41];    /* contact title */
  670.     char rd_company[41];    /* company name */
  671.     char rd_addr[2*40+1];    /* company address */
  672.     char rd_city[26];        /* city */
  673.     char rd_state[3];        /* state */
  674.     char rd_zip[11];        /* zip code */
  675.     char rd_phone[13];    /* phone number */
  676.     char rd_ext[5];        /* phone extension */
  677.     char rd_fax[13];        /* fax number */
  678.     char rd_notes[4*40+1];    /* notes */
  679. } rolodeck_t;
  680.  
  681. /* field names */
  682. #define RD_CONTACT     (0)
  683. #define RD_TITLE         (1)
  684. #define RD_COMPANY     (2)
  685. #define RD_ADDR         (3)
  686. #define RD_CITY         (4)
  687. #define RD_STATE         (5)
  688. #define RD_ZIP         (6)
  689. #define RD_PHONE         (7)
  690. #define RD_EXT         (8)
  691. #define RD_FAX         (9)
  692. #define RD_NOTES        (10)
  693. #define RDFLDC        (11)
  694.  
  695. /* field definition list */
  696. extern const cbfield_t rdfldv[RDFLDC];
  697.  
  698. #endif  /* #ifndef ROLODECK_H */
  699.  
  700.                 Figure 4.1. rolodeck.h
  701.  
  702.  
  703. #ifndef ROLODECK_I  /* prevent multiple includes */
  704. #define ROLODECK_I
  705.  
  706. #include <cbase.h>
  707. #include <stddef.h>
  708. #include "rolodeck.h"
  709.  
  710. /* field definition list */
  711. static cbfield_t rd_fldv[] = {
  712.     {offsetof(rolodeck_t, rd_contact),
  713.      sizeofm(rolodeck_t, rd_contact),
  714.      t_string, CB_FKEY | CB_FUNIQ, "rdcont.ndx"},
  715.     {offsetof(rolodeck_t, rd_title),
  716.      sizeofm(rolodeck_t, rd_title),
  717.      t_string, 0, ""},
  718.     {offsetof(rolodeck_t, rd_company),
  719.      sizeofm(rolodeck_t, rd_company),
  720.      t_string, CB_FKEY, "rdcomp.ndx"},
  721.     {offsetof(rolodeck_t, rd_addr),
  722.      sizeofm(rolodeck_t, rd_addr),
  723.      t_string, 0, ""},
  724.     {offsetof(rolodeck_t, rd_city),
  725.      sizeofm(rolodeck_t, rd_city),
  726.      t_string, 0, ""},
  727.     {offsetof(rolodeck_t, rd_state),
  728.      sizeofm(rolodeck_t, rd_state),
  729.      t_string, 0, ""},
  730.     {offsetof(rolodeck_t, rd_zip),
  731.      sizeofm(rolodeck_t, rd_zip),
  732.      t_string, 0, ""},
  733.     {offsetof(rolodeck_t, rd_phone),
  734.      sizeofm(rolodeck_t, rd_phone),
  735.      t_string, 0, ""},
  736.     {offsetof(rolodeck_t, rd_ext),
  737.      sizeofm(rolodeck_t, rd_ext),
  738.      t_string, 0, ""},
  739.     {offsetof(rolodeck_t, rd_fax),
  740.      sizeofm(rolodeck_t, rd_fax),
  741.      t_string, 0, ""},
  742.     {offsetof(rolodeck_t, rd_notes),
  743.      sizeofm(rolodeck_t, rd_notes),
  744.      t_string, 0, ""}
  745. };
  746.  
  747. #endif    /* #ifdef ROLODECK_I */
  748.  
  749.                 Figure 4.2. rolodeck.i
  750.  
  751.      Next is the type definition for the record being defined.  Each
  752. field in the record (member in the structure) begins with a character
  753. sequence derived from the cbase name, usually two characters, followed
  754. by an underscore.  This character sequence should be unique across all
  755. cbase record types used by a given program.  This type is for use when
  756. declaring record variables.  Variables of this type should be used for
  757. functions such as cbgetr requiring a pointer to a record.
  758.  
  759.      Following the record type definition is a list of macros used to
  760. identify a field when calling cbase functions.  The names of these
  761. macros are constructed by taking the field names from the record type
  762. definition and converting them to uppercase; this is why the field names
  763. must be made unique.  Each field name macro must be an integer
  764. indicating the number of the field in the record type definition, starting
  765. at zero.  A macro for the total number of fields is also defined.  This
  766. macro is to be used for the fldc argument when calling cbcreate
  767. and cbopen. Finally, the field definition array is declared.  The
  768. elements of the field definition list are of type cbfield_t, defined in
  769. <cbase.h>.
  770.  
  771.      Since more than one source file will in general need to include the
  772. same header, the actual definition of the field definition array must be
  773. placed in a separate file.  This file is given a .i extension to indicate
  774. that it contains data or code as opposed to a .h file containing only
  775. definitions, and it is included by only one source file, normally the one
  776. containing main.  rolodeck.i is shown in figure 4.2.
  777.  
  778.      The field definition array specifies, for each field in the record,
  779. that field's offset in the record, size, type, some flags, and a filename
  780. to be used if the field is to be a key.  The offsetof macro should
  781. be used to specify the offset.  A macro sizeofm (size of member) is
  782. defined in <blkio.h> to give the size of a member of a structure.
  783.  
  784.      size_t sizeofm(struct_t, member);
  785.  
  786. The arguments to sizeofm are the same as for offsetof.  The size
  787. cannot be calculated from the difference between field offsets because
  788. padding characters may be added between structure members to
  789. maintain proper alignment (see KERN88).  The field definition array is
  790. to be used as the fldv argument to cbcreate and cbopen.
  791.  
  792.      It should be noted that every record type should normally have at
  793. least one unique key field.  This field is used to uniquely identify
  794. records.  The physical record position is often used for this purpose,
  795. but, as will be discussed later in this chapter, a unique key is required
  796. for multiuser applications.
  797.  
  798.      In addition to the data definition header, all programs using cbase
  799. normally require that the following header files also be included.
  800.  
  801.     #include <blkio.h>
  802.     #include <cbase.h>
  803.     #include <errno.h>
  804.  
  805. <blkio.h> is the header for the blkio library.  It is included to
  806. provide the definition of the NULL pointer and the declaration of the
  807. function bexit.  bexit is for use in place of exit in any program
  808. using the blkio library for file access.  It writes out any buffered data
  809. for any open block file then calls exit (see bexit in the blkio
  810. reference manual).  <cbase.h> is the cbase header file containing all
  811. the constant and type definitions and function declarations for using the
  812. cbase library.  <errno.h> contains the declaration of errno as well
  813. as definitions of error code values; all cbase functions use errno for
  814. error reporting.  
  815.  
  816.  
  817. 4.2  Opening a cbase
  818.  
  819.      The first step in accessing an existing cbase is to open it.  Figure
  820. 4.3 shows the code from rolodeck.c to open the rolodeck cbase. 
  821. rolodeck is opened with a type argument of "r+" to allow both
  822. reading and writing.  The other arguments are the cbase name,
  823. ROLODECK, the field count, RDFLDC, and the field definition list,
  824. rdfldv, all defined in the data definition header file, rolodeck.h. 
  825. On error cbopen returns the NULL pointer.  For this program there is
  826. only one cbase, but most applications will require more.
  827.  
  828.      If the named cbase does not exist, cbopen will fail and set errno
  829. to ENOENT.  In this example, if the rolodeck cbase does not exist, it is
  830. created and the program continues as normal.  Note that the cbase must
  831. still be opened after it is created.  In some cases a separate program is
  832. written to create all the cbases required by an application; in this case
  833. the main program would probably interpret ENOENT as an error.
  834.  
  835. /* open rolodeck cbase */
  836. cbp = cbopen(ROLODECK, "r+", RDFLDC, rdfldv);
  837. if (cbp == NULL) {
  838.     if (errno != ENOENT) {
  839.         bexit(EXIT_FAILURE);
  840.     }
  841.     /* create rolodeck cbase */
  842.     printf("Rolodeck does not exist.  Creating...\n");
  843.     if (cbcreate(ROLODECK, sizeof(rolodeck_t), RDFLDC, rdfldv)
  844.                             == -1) {
  845.         bexit(EXIT_FAILURE);
  846.     }
  847.     cbp = cbopen(ROLODECK, "r+", RDFLDC, rdfldv);
  848.     if (cbp == NULL) {
  849.         bexit(EXIT_FAILURE);
  850.     }
  851. }
  852.  
  853.               Figure 4.3. Opening a cbase
  854.  
  855.  
  856. 4.3  Locking a cbase
  857.  
  858.      Before accessing an open cbase, it must first be locked.  If data is
  859. to be written to the cbase, it must be write locked, otherwise only a
  860. read lock is required.  A cbase can be read locked by more than one
  861. process at the same time, and read locks are therefore also called
  862. shared locks.  A write lock, on the other hand, is an exclusive lock; a
  863. write locked cbase can be neither read nor write locked by any other
  864. process.  Write locks are exclusive because, if one process tried to read
  865. data while it was partially modified by another, the data would probably
  866. be in an inconsistent state.  Processes that will only read data, however,
  867. can safely do so concurrently.
  868.  
  869.      While a cbase is write locked, other processes needing to access
  870. that cbase must wait until it is unlocked so that they can in turn lock it
  871. themselves to complete their processing.  While a cbase is read locked,
  872. only processes needing to write must wait.  Using a write lock when a
  873. read lock would suffice will therefore delay other processes
  874. unnecessarily.  Locks of either type should be held for the shortest time
  875. possible; a common mistake in writing multiuser applications is to
  876. pause for use input while holding a lock, causing that lock to be held
  877. indefinitely.
  878.  
  879.      If an attempt is made to obtain a lock on a cbase, but is blocked
  880. by a lock held by another process, cblock will fail and set errno to
  881. EAGAIN.  The call to cblock is therefore usually made in a loop with a
  882. predefined maximum number of tries.  It is convenient to place this in
  883. a function configured for the application being developed.  Figure 4.4
  884. shows this function from rolodeck.c.  It may also be suitable in
  885. some instances to sleep for a short (possibly random) time between
  886. attempts to lock.
  887.  
  888. #define LMAXTRIES (20)    /* max lock tries */
  889.  
  890. /* rdlock:  rolodeck lock */
  891. int rdlock(cbase_t *cbp, int ltype)
  892. {
  893.     for (int i = 0; i < LMAXTRIES; ++i) {
  894.         if (cblock(cbp, ltype) == -1) {
  895.             if (errno == EAGAIN) {
  896.                 continue;
  897.             }
  898.             return -1;
  899.         } else {
  900.             errno = 0;
  901.             return 0;
  902.         }
  903.     }
  904.     errno = EAGAIN;
  905.     return -1;
  906. }
  907.  
  908.          Figure 4.4. Rolodeck Locking Function
  909.  
  910.      There are also two lock types (CB_RDLKW and CB_WRLCKW)
  911. which, if the requested lock is blocked, will wait until it can be
  912. obtained.  These are not usually used, however, because if the lock
  913. does not become free in a reasonable time, the process waiting for the
  914. lock will be hung.
  915.  
  916.      For an applications where there will be only a single process, the
  917. necessary locks can be set immediately after opening the cbases to be
  918. accessed and left locked.
  919.  
  920.      One critical concern when locking multiple cbases is the possibility
  921. of deadlock.  Deadlock is an extensive subject, and there are a number
  922. of ways of dealing with it.  Most texts on operating systems (see
  923. CALI82) and database theory cover the subject in detail.
  924.  
  925.  
  926. 4.4  Accessing a cbase
  927.  
  928.      The gross structure of the rolodeck program is a case statement
  929. within a loop.  At the start of the loop a user request is read and used
  930. to select the action performed in the case statement.  Each individual
  931. action performed in the case statement illustrates the use of cbase to
  932. perform a basic operation, e.g., inserting a record, deleting a record,
  933. finding the next record, exporting data to a text file, etc.  The operation
  934. of finding the next record serves as a good general example.  The code
  935. for this from rolodeck.c is shown in figure 4.5.
  936.  
  937.      One of the most important points to notice in the example code is
  938. that a unique key (the contact name, here) is used to relocate the
  939. current record when a cbase is locked.  This is because, when a cbase
  940. is unlocked, it may be modified by another process.  A record at a
  941. given location may be deleted, and the empty slot possibly reused for a
  942. new record.  Because of this, cbsetrpos cannot be used with a
  943. record position obtained during a previously held lock.  As mentioned
  944. in the section 4.3, applications that do not involve multiple processes
  945. accessing the same data can simply lock a cbase and leave it locked
  946. rather than locking the cbase immediately prior to each transaction.
  947.  
  948.      Another central point is the use of multiple keys.  In the rolodeck
  949. program, both the contact and the company names are keys.  A variable
  950. sf is used in rolodeck.c to identify the current sort field, which
  951. can be changed interactively.  Before using the cbkeynext function,
  952. the appropriate key cursor must first be positioned.  cbkeysrch
  953. positions only the key being searched, here being the unique key.  If
  954. the next card is to be found using the sort order of a different key,
  955. cbkeyalign must first be used to align that key cursor with the
  956. current record.
  957.  
  958. case REQ_NEXT_CARD:    /* next card */
  959.     rdlock(cbp, CB_RDLCK);
  960.     if (cbreccnt(cbp) == 0) {
  961.         printf("The rolodeck is empty.\n\n");
  962.         rdlock(cbp, CB_UNLCK);
  963.         continue;
  964.     }
  965.     /* use unique key field to set rec cursor */
  966.     found=cbkeysrch(cbp,RD_CONTACT,rd.rd_contact);
  967.     if (sf != RD_CONTACT) {
  968.         /* align cursor of sort key */
  969.         cbkeyalign(cbp, sf);
  970.     }
  971.     if (found == 1) {
  972.         /* advance key (and rec) cursor 1 pos */
  973.         cbkeynext(cbp, sf);
  974.     }
  975.     if (cbrcursor(cbp) == NULL) {
  976.         printf("End of deck.\n\n");
  977.         rdlock(cbp, CB_UNLCK);
  978.         continue;
  979.     }
  980.     cbgetr(cbp, &rd);
  981.     rdlock(cbp, CB_UNLCK);
  982.     break;    /* case REQ_NEXT_CARD: */
  983.  
  984.            Figure 4.5. Next Rolodeck Record
  985.  
  986.  
  987. 4.5  Closing a cbase
  988.  
  989.      When a program is through accessing a cbase, the cbase should be
  990. closed.  Figure 4.6 shows this code from rolodeck.c.
  991.  
  992.     /* close cbase */
  993.     if (cbclose(cbp) == -1) {
  994.         bexit(EXIT_FAILURE);
  995.     }
  996.  
  997.               Figure 4.6. Closing a cbase
  998.  
  999. A cbase is automatically unlocked when it is closed.  A cbase is
  1000. normally, but not necessarily, opened and closed only once in a
  1001. program.
  1002.  
  1003.  
  1004. 4.6  Manipulating Exported Data
  1005.  
  1006.      Exported data often requires some processing before it is used. 
  1007. For instance, consider modifying an application to add a new field to an
  1008. existing cbase.  If there is a considerable amount of data already stored,
  1009. it would be desirable to use the import/export functions rather than
  1010. manually entering the data again.  Another common example is moving
  1011. data between cbase and another database system, which may use
  1012. different field and record separators for exported data.
  1013.  
  1014.      An ideal tool for processing records in text files is awk.  Figure
  1015. 4.7 shows an awk program for inserting a new field at position two in
  1016. all the records in a text file (note that awk field numbering starts at
  1017. one, not zero).
  1018.  
  1019.     The predefined variables FS and OFS are used to set the input and
  1020. output field separators, respectively.  The predefined variables RS and
  1021. ORS are used to set the input and output record separators, respectively. 
  1022. Setting these variables appropriately is all that is necessary to convert
  1023. between text file formats using different field and record separators. 
  1024. The awk program in figure 4.8 converts text files exported from a
  1025. database using the tab character as a field separator for import by
  1026. cbase.
  1027.  
  1028. BEGIN {
  1029.     FS = "|";        # set input and output field
  1030.     OFS = FS;        # and record separators
  1031.     RS = "\n";
  1032.     ORS = RS;
  1033.     NEWFIELD = 2;    # field to insert
  1034. }
  1035.  
  1036. # insfld:  insert field n of current record
  1037. function insfld(n)
  1038. {
  1039.     if (n < 1 || n > NF + 1) {
  1040.         return -1;
  1041.     }
  1042.  
  1043.     for (i = NF; i >= n; --i) {
  1044.         $(i + 1) = $i;
  1045.     }
  1046.     $n = "";
  1047.  
  1048.     return 0;
  1049. }
  1050.  
  1051. {
  1052.     if (insfld(NEWFIELD) == -1) {
  1053.         printf "Error inserting new 2nd fld.\n";
  1054.         exit 1;
  1055.     }
  1056.     print $0;
  1057. }
  1058.  
  1059. END {
  1060.     exit 0;
  1061. }
  1062.  
  1063.         Figure 4.7. awk Program to Insert Field
  1064.  
  1065.  
  1066. BEGIN {
  1067.     FS = "\t";        # set input and output field
  1068.     OFS = "|";        # and record separators
  1069.     RS = "\n";
  1070.     ORS = RS;
  1071. }
  1072.  
  1073. {
  1074.     print $0
  1075. }
  1076.  
  1077. END {
  1078.     exit 0;
  1079. }
  1080.  
  1081.    Figure 4.8. awk Program to Change Field Separator
  1082.  
  1083.  
  1084.  
  1085.  
  1086.                 Appendix A:  Installation Instructions
  1087.  
  1088.      The cbase library is distributed in MS-DOS format on either two
  1089. 360K 5.25" diskettes or one 720K 3.5" diskette.  On the distribution
  1090. diskettes is a directory containing the files for each library and for
  1091. rolodeck, the example program.  There is also a directory for manx, the
  1092. utility used to extract an on-line copy of the reference manual for each
  1093. library, and a directory containing installation batch files for different
  1094. MS-DOS compilers.
  1095.  
  1096.      blkio        blkio library
  1097.      btree        btree library
  1098.      lseq        lseq library
  1099.      cbase        cbase library
  1100.      manx        manx utility
  1101.      rolodeck    example program
  1102.      bats        batch files for different compilers
  1103.  
  1104.      If cbase is being installed on an MS-DOS system, the following
  1105. two commands will copy the contents of the distribution diskettes onto
  1106. the main drive.
  1107.  
  1108.      > mkdir cbase
  1109.      > xcopy a:\ cbase /s /v
  1110.  
  1111. An operating system besides MS-DOS will require either a facility to
  1112. read MS-DOS diskettes or access to an MS-DOS machine from which
  1113. files can be transferred by a serial link or network to the target
  1114. machine.  If the transfer process does not automatically convert the text
  1115. files to the format of the target system, an additional conversion utility
  1116. may be necessary.
  1117.  
  1118.      The second step in the installation is set a switch specifying the
  1119. host operating system.  This switch is the HOST macro in the blkio file
  1120. blkio_.h.  If the host operating system is MS-DOS, the MSDOSC
  1121. macro in the same file must also be set to specify the C compiler being
  1122. used.
  1123.  
  1124.      If the compiler being used is not completely ANSI compatible,
  1125. some additional switches must also be set.  These are a set of macros
  1126. located in the blkio header file blkio.h, each of which specifies a
  1127. particular ANSI feature.  For instance, the macro AC_PROTO is used to
  1128. indicate function prototype support.
  1129.  
  1130.      The remaining installation instructions for each library, which
  1131. should be performed in the directory containing the files for that library,
  1132. are given below.  Before proceeding, the manx utility should be
  1133. compiled and placed in a directory in the path.  After all the libraries
  1134. have been installed, the final step is to compile the example program;
  1135. the instructions for doing this are given in the example program's
  1136. readme file.
  1137.  
  1138.      The MS-DOS installation batch files, install.bat, each take
  1139. two arguments.  The first specifies the memory model, legal values for
  1140. which are s, m, c, l, and h; the library file is named libm.lib,
  1141. where lib would be the library name and m would correspond to the
  1142. memory model of the library.  The second, if present, causes the
  1143. reference manual to be extracted from the source code into the file
  1144. lib.man, where lib would again be the library name.  The main
  1145. batch file included with each library is written for Borland Turbo C. 
  1146. Because there is so little uniformity among C compilers for MS-DOS,
  1147. modifications will be required for other compilers.  Instructions for
  1148. making these straightforward modifications are given at the beginning of
  1149. each install.bat.  Some batch files modified for other compilers
  1150. can be found in the bats directory.  Also, if a make utility is
  1151. available, the UNIX makefiles may instead be adapted.
  1152.  
  1153.  
  1154. A1.  The blkio Library
  1155.  
  1156.                          UNIX
  1157.  
  1158.      1. Set the HOST macro in blkio_.h to UNIX.
  1159.      2. Install the boolean header file.
  1160.              $ su
  1161.              # cp bool.h /usr/include
  1162.              # ^d
  1163.      3. Extract the on-line reference manual.
  1164.              $ make man
  1165.      4. Build the blkio library.
  1166.              $ make blkio
  1167.      5. Install the blkio library.  This will copy the blkio header
  1168.         file blkio.h to /usr/include and the blkio library archive
  1169.         to /usr/lib.
  1170.              $ su
  1171.              # make install
  1172.              # ^d
  1173.  
  1174.  
  1175.                         MS-DOS
  1176.  
  1177.      1. Set the HOST macro in blkio_.h to MS_DOS.
  1178.      2. Set the MSDOSC macro in blkio_.h to the C compiler being
  1179.         used.
  1180.      3. If necessary, modify install.bat for the C compiler being
  1181.         used.
  1182.      4. Extract the reference manual and build and install the blkio
  1183.         library.
  1184.              > install s x
  1185.         Run again for each additional memory model desired.
  1186.  
  1187.  
  1188. A2.  The btree Library
  1189.  
  1190.                          UNIX
  1191.  
  1192.      1. Install the blkio library.
  1193.      2. Extract the on-line reference manual.
  1194.              $ make man
  1195.      3. Build the btree library.
  1196.              $ make btree
  1197.      4. Install the btree library.  This will copy btree.h to
  1198.         /usr/include and the btree library archive to
  1199.         /usr/lib.
  1200.              $ su
  1201.              # make install
  1202.              # ^d
  1203.  
  1204.  
  1205.                         MS-DOS
  1206.  
  1207.      1. Install the blkio library.
  1208.      2. If necessary, modify install.bat for the C compiler
  1209.         being used.
  1210.      3. Install the btree library.
  1211.              > install s x
  1212.         Run again for each additional memory model desired.
  1213.  
  1214.  
  1215. A3.  The lseq Library
  1216.  
  1217.                          UNIX
  1218.  
  1219.      1. Install the blkio library.
  1220.      2. Extract the on-line reference manual.
  1221.              $ make man
  1222.      3. Build the lseq library.
  1223.              $ make lseq
  1224.      4. Install the lseq library.  This will copy lseq.h to
  1225.         /usr/include and the lseq library archive to
  1226.         /usr/lib.
  1227.              $ su
  1228.              # make install
  1229.              # ^d
  1230.  
  1231.  
  1232.                         MS-DOS
  1233.  
  1234.      1. Install the blkio library.
  1235.      2. If necessary, modify install.bat for the C compiler
  1236.         being used.
  1237.      3. Install the lseq library.
  1238.              > install
  1239.         Run again for each additional memory model desired.
  1240.  
  1241.  
  1242. A4.  The cbase library
  1243.  
  1244.                          UNIX
  1245.  
  1246.      1. Install the btree and lseq libraries.
  1247.      2. Extract the on-line reference manual.
  1248.              $ make man
  1249.      3. Build the cbase library.
  1250.              $ make cbase
  1251.      4. Install the cbase library.  This will copy cbase.h to
  1252.         /usr/include and the cbase library archive to
  1253.         /usr/lib.
  1254.              $ su
  1255.              # make install
  1256.              # ^d
  1257.  
  1258.  
  1259.                         MS-DOS
  1260.  
  1261.      1. Install the btree and lseq libraries.
  1262.      2. If necessary, modify install.bat for the C compiler
  1263.         being used.
  1264.      3. Install the cbase library.
  1265.              > install s x
  1266.         Run again for each additional memory model desired.
  1267.  
  1268.  
  1269.  
  1270.  
  1271.  
  1272.  
  1273.  
  1274.                   Appendix B:  Defining New Data Types
  1275.  
  1276.  
  1277.      cbase is designed to allow custom data types to be defined by the
  1278. user.  Custom data types are completely integrated, becoming
  1279. indistinguishable from those predefined.  A data type definition consists
  1280. of a macro used as the type name (e.g., t_string), and three
  1281. functions:  a comparison function, an export function, and an import
  1282. function.  The comparison function is the most important; it determines
  1283. the sort order for data of that type.  The export function is used to
  1284. export data of the associated type to a text file, and the import function
  1285. to import data.  Below are given step-by-step instructions for defining a
  1286. new cbase data type.
  1287.  
  1288.  
  1289. B1.  The Type Name
  1290.  
  1291.      For each cbase data type there is a corresponding type name by
  1292. which the user refers to that data type.  The type names are defined in
  1293. cbase.h.
  1294.  
  1295.     #define t_char     (0)    /* signed character */
  1296.     ...
  1297.     #define t_binary    (26)    /* binary data */
  1298.  
  1299. Type names must be defined as integers, starting at zero and increasing
  1300. in steps of one.  The type name for a new data type would be added at
  1301. the end of this list, and be defined as an integer one greater than the
  1302. last data type in the list.  To avoid possible conflict with future
  1303. predefined types, user defined type names should not start with t_. 
  1304. The prefix ut_ is recommended for user defined types.
  1305.  
  1306.     #define ut_new    (27)    /* new data type */
  1307.  
  1308.  
  1309. B2.  The Comparison Function
  1310.  
  1311.      A data type is characterized primarily by its sort order.  Each data
  1312. type is given a comparison function defining this sort order. 
  1313. Comparison functions are of the form
  1314.  
  1315.  int cmp(const void *p1, const void *p2, size_t n);
  1316.  
  1317. p1 and p2 are pointers to two data items to be compared, and n is the
  1318. size of the data items.  The value returned must be less than, equal to,
  1319. or greater than zero if the data item pointed to by p1 is less than,
  1320. equal to, or greater than, respectively, that pointed to by p2.  The C
  1321. standard library function memcmp would be a valid cbase comparison
  1322. function.
  1323.  
  1324.      All cbase comparison functions are located in the file cbcmp.c. 
  1325. For a new data type, a comparison function would be added in this file. 
  1326.  
  1327. static int newcmp(const void *p1, const void *p2,   
  1328.                                         size_t n)
  1329. {
  1330.     ...
  1331. }
  1332.  
  1333. Comparison functions are made static because they are accessed by
  1334. cbase only through an array of function pointers, cbcmpv, also defined
  1335. in cbcmp.c.  This array contains the comparison function for each
  1336. cbase data type.  The integer value of the type name is used by cbase
  1337. as an index into this array, and so the comparison functions must be in
  1338. the same order as the type names.  A pointer to the comparison
  1339. function for a new data type would be added at the end of this array.
  1340.  
  1341.     /* cbase comparison function table */
  1342.     cbcmp_t cbcmpv[] = {
  1343.         charcmp,
  1344.         ...
  1345.         binarycmp,
  1346.         newcmp
  1347.     };
  1348.  
  1349.  
  1350. B3.  The Export and Import Functions
  1351.  
  1352.      Each data type has an associated export function.  This export
  1353. function takes a data item of the associated type and writes it to a file
  1354. in a text format.  Export functions are of the form
  1355.  
  1356.     int exp(FILE *fp, const void *p, size_t n);
  1357.  
  1358. p is a pointer to the data item of size n to be exported.  The export
  1359. function converts the data item to a text format, then writes it to the
  1360. current position in file fp.  Upon successful completion, a value of
  1361. zero is returned.  Otherwise, a value of -1 is returned.  See cbexport
  1362. in the cbase Programmer's Reference Manual for a description of the
  1363. format of exported data.
  1364.  
  1365.      All cbase export functions are located in the file cbexp.c.  For a
  1366. new data type, an export function would be added in this file.
  1367.  
  1368. static int newexp(FILE *fp, const void *p, size_t n)
  1369. {
  1370.     ...
  1371. }
  1372.  
  1373.      Just like comparison functions, export functions are accessed by
  1374. cbase through an array.  This array, cbexpv, is defined in cbexp.c. 
  1375. A pointer to the export function for the new data type would be added
  1376. at the end of this array.
  1377.  
  1378.      The import function reads a data item from a text file.  Import
  1379. functions are of the form
  1380.  
  1381.     int imp(FILE *fp, const void *p, size_t n);
  1382.  
  1383. The parameters and return value are the same as for the export
  1384. function.  Import functions are located in cbimp.c.  Pointers to the
  1385. import functions are stored in the array cbimpv.
  1386.  
  1387.  
  1388. B4.  The Type Count
  1389.  
  1390.      The macro CBTYPECNT is defined in cbase_.h as the number
  1391. of data types defined.  It must be incremented by one for each new
  1392. data type added.
  1393.  
  1394.  
  1395.      After completing these steps, rebuild the cbase library (see
  1396. Appendix A).  The underlying libraries do not need to be rebuilt.
  1397.  
  1398.  
  1399.  
  1400.  
  1401.         Appendix C:  Porting to a New Operating System
  1402.  
  1403.  
  1404.      The blkio library provides a means for portable access to
  1405. structured files just as the stdio library does for text files.  blkio is thus
  1406. the only library requiring modification to port to a new operating
  1407. system.  The steps necessary to perform this port are outlined below.
  1408.  
  1409.  
  1410. C1.  The HOST Macro
  1411.  
  1412.      In the blkio library's private header file blkio_.h, a macro is
  1413. defined for each supported operating system.  When installing the blkio
  1414. library, the host operating system is selected by defining the HOST
  1415. macro using one of these system macros.  When porting to a new
  1416. operating system, a new system macro must be defined in blkio_.h.
  1417.  
  1418.     #define UNIX    (1)     /* UNIX */
  1419.     #define MS_DOS  (2)     /* MS-DOS */
  1420.     #define NEWOS   (3)     /* new os */
  1421.     #define HOST    NEWOS
  1422.  
  1423.      In some instances it will be necessary to take into account
  1424. variations among the C compilers available for a system.  For
  1425. MS-DOS, the macro MSDOSC, also defined in blkio_.h, is used to
  1426. select a compiler in the same way as the HOST macro is used to select
  1427. the operating system.  To port to a new C compiler under MS-DOS, a
  1428. new compiler macro must be defined.
  1429.  
  1430.     #define TURBOC  (1)     /* Turbo C */
  1431.     #define MSC     (2)     /* Microsoft C */
  1432.     #define NEWC    (3)     /* new C compiler */
  1433.     #define MSDOSC  NEWC
  1434.  
  1435. To port to multiple incompatible compilers under a new operating
  1436. system, a new compiler selection macro analogous to MSDOSC would
  1437. need to be defined.
  1438.  
  1439.  
  1440. C2.  The File Descriptor Type
  1441.  
  1442.      In most operating systems, an open file is accessed not by name,
  1443. but through some type of handle, usually called a file descriptor.  File
  1444. descriptors are normally of type int, but this is not guaranteed.  A
  1445. union is therefore defined (in blkio.h) for the file descriptor.
  1446.  
  1447.     typedef union {    /* fildes type */
  1448.         char cfd;    /* character fildes */
  1449.         short sfd;    /* short int fildes */
  1450.         int ifd;    /* int fildes */
  1451.     } fd_t;
  1452.  
  1453. This type is used to define the fd member of the BLKFILE structure.
  1454.  
  1455.     typedef struct {    /* block file ctl struct */
  1456.         fd_t fd;    /* file descriptor */
  1457.         ...
  1458.     } BLKFILE;
  1459.  
  1460. When modifying the code in subsequent sections, the appropriate
  1461. member of the union fd_t would be used to access a file descriptor. 
  1462. If the file descriptor type for the new system is short, for instance,
  1463. the file descriptor for BLKFILE *bp would be accessed as bp->fd.sfd. 
  1464. It will be necessary to add a member to the fd_t union if one of the
  1465. required type does not already exist.
  1466.  
  1467.  
  1468. C3.  File Access System Calls
  1469.  
  1470.      The bulk of the operating system specific code is related to the
  1471. system calls used to access the file system.  These system calls perform
  1472. basic operations such as opening, reading, and writing a file, and are
  1473. conceptually the same on most systems.  In fact, they can usually be
  1474. directly translated to a corresponding call on the new system.
  1475.  
  1476.      All system calls accessing the file system are isolated in the file
  1477. buops.c (blkio unbuffered operations).  The HOST macro is used to
  1478. separate sections of code used for different operating systems. 
  1479. Similarly, compiler selection macros such as MSDOSC are used to
  1480. separate sections of code for different compilers under the same
  1481. operating system.
  1482.  
  1483.     #if HOST == UNIX
  1484.         /* code for UNIX */
  1485.         .
  1486.         .
  1487.     #elif HOST == MS_DOS
  1488.         /* code for MS-DOS */
  1489.     #if MSDOSC == TURBOC
  1490.         /* code for Turbo C */
  1491.         .
  1492.         .
  1493.     #elif MSDOSC == MSC
  1494.         /* code for Microsoft C */
  1495.         .
  1496.         .
  1497.     #endif
  1498.     #endif
  1499.  
  1500. When porting to a new operating system (or compiler), each of these
  1501. conditional compilations using HOST (or the relevant compiler selection
  1502. macro) must be located and an additional #elif added for the new
  1503. system (or compiler).
  1504.  
  1505.  
  1506. C4.  File Locking System Calls
  1507.  
  1508.      System calls are also used to perform file locking.  All system
  1509. calls for file locking are located in the file lockb.c.  This file must
  1510. be modified as was buops.c.  If file locking will not be used on the
  1511. new system, lockb.c need not be altered.
  1512.  
  1513.  
  1514.  
  1515.  
  1516.                                             References
  1517.  
  1518.  
  1519. CALI82    Calingaert, P. Operating System Elements. Englewood
  1520.     Cliffs, NJ: Prentice Hall, 1982.
  1521.  
  1522. COME79    Comer, D. The Ubiquitous B-tree. ACM Computing
  1523.     Surveys, June 1979.
  1524.  
  1525. FROS89    Frost, L. A Buffered I/O Library for Structured Files.
  1526.     The C Users Journal, October 1989.
  1527.  
  1528. HORO76    Horowitz, E. and S. Sahni. Fundamentals of Data
  1529.     Structures. Rockville, MD: Computer Science Press, 1976.
  1530.  
  1531. KERN88    Kernighan, B. and D. Ritchie. The C Programming
  1532.     Language. Englewood Cliffs, NJ: Prentice Hall, 1988.
  1533.  
  1534. KNUT68    Knuth D. The Art of Computer Programming Volume 3 /
  1535.     Sorting and Searching. Reading, MA: Addison-Wesley, 1968.
  1536.  
  1537. ULLM82    Ullman, J. Principles of Database Systems. Rockville,
  1538.      MD: Computer Science Press, 1982.